<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>IP地址查询工具</title>
<style>
/* 保持原有样式不变 */
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    font-family: 'Inter', 'Microsoft YaHei', 'PingFang SC', sans-serif;
}
:root {
    --primary: #2563eb;
    --primary-light: #3b82f6;
    --primary-dark: #1d4ed8;
    --secondary: #64748b;
    --neutral: #f8fafc;
    --neutral-dark: #1e293b;
    --card-bg: #ffffff;
    --border: #e2e8f0;
    --success: #10b981;
    --error: #ef4444;
    --shadow-sm: 0 2px 6px rgba(0,0,0,0.05);
    --shadow: 0 4px 12px rgba(0,0,0,0.08);
    --shadow-md: 0 8px 24px rgba(0,0,0,0.12);
    --radius-sm: 6px;
    --radius: 12px;
    --radius-lg: 16px;
    --transition: all 0.3s ease;
}
body {
    background: linear-gradient(145deg, #f0f4ff 0%, #f8fafc 100%);
    color: var(--neutral-dark);
    line-height: 1.6;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    padding: 20px 0;
}
.container {
    max-width: 900px;
    width: 100%;
    margin: 0 auto;
    padding: 0 20px;
    flex: 1;
}
.header {
    text-align: center;
    margin: 40px 0 30px;
    padding-bottom: 20px;
    border-bottom: 1px solid var(--border);
}
.header h1 {
    color: var(--neutral-dark);
    font-size: 2.5rem;
    font-weight: 700;
    margin-bottom: 8px;
    letter-spacing: -0.5px;
}
.header p {
    color: var(--secondary);
    font-size: 1.1rem;
    max-width: 600px;
    margin: 0 auto;
}
.card {
    background: var(--card-bg);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow);
    padding: 40px;
    margin-bottom: 15px;
    border: 1px solid var(--border);
    transition: var(--transition);
}
.card:hover {
    box-shadow: var(--shadow-md);
}
.query-form {
    display: flex;
    gap: 15px;
    margin-bottom: 25px;
    align-items: center;
}
#ipInput {
    flex: 1;
    padding: 16px 20px;
    border: 2px solid var(--border);
    border-radius: var(--radius);
    font-size: 16px;
    transition: var(--transition);
    background: var(--neutral);
}
#ipInput:focus {
    outline: none;
    border-color: var(--primary-light);
    box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
}
#queryBtn {
    padding: 16px 30px;
    background: linear-gradient(135deg, var(--primary-light) 0%, var(--primary-dark) 100%);
    color: white;
    border: none;
    border-radius: var(--radius);
    cursor: pointer;
    font-size: 16px;
    font-weight: 600;
    transition: var(--transition);
    box-shadow: 0 4px 15px rgba(37, 99, 235, 0.2);
}
#queryBtn:hover {
    background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 100%);
    transform: translateY(-2px);
    box-shadow: 0 6px 20px rgba(37, 99, 235, 0.3);
}
.quick-actions {
    display: flex;
    gap: 12px;
    margin-top: 20px;
    flex-wrap: wrap;
}
.quick-btn {
    padding: 10px 18px;
    background: var(--neutral);
    border: 1px solid var(--border);
    border-radius: var(--radius-sm);
    cursor: pointer;
    font-size: 14px;
    color: var(--secondary);
    transition: var(--transition);
    font-weight: 500;
}
.quick-btn:hover {
    background: #e0e7ff;
    border-color: var(--primary-light);
    color: var(--primary-dark);
}
.quick-btn.active {
    background: #dbeafe;
    border-color: var(--primary);
    color: var(--primary-dark);
}
.result-container {
    margin-top: 1px;
}
.result-card {
    background: #ffffff !important;
    box-shadow: 0 2px 8px rgba(0,0,0,0.06);
    border-radius: var(--radius);
    padding: 20px 30px;
    margin-bottom: 15px;
    line-height: 1.5;
    border: 1px solid var(--border);
}
.ip-type-badge {
    display: inline-block;
    padding: 4px 8px;
    border-radius: 4px;
    font-size: 12px;
    font-weight: 600;
    margin-left: 10px;
}
.ipv4-badge {
    background: #dbeafe;
    color: #3b82f6;
}
.ipv6-badge {
    background: #d1fae5;
    color: #10b981;
}
.info-item {
    padding: 12px 0;
    display: flex;
    border-bottom: 1px solid var(--border);
    align-items: center;
}
.info-item:last-child {
    border-bottom: none;
}
.info-label {
    width: 140px;
    font-weight: 600;
    color: var(--secondary);
    font-size: 15px;
}
.info-value {
    flex: 1;
    font-size: 16px;
    color: var(--neutral-dark);
}
.ipv6-tip {
    color: var(--secondary);
    background: #f8fafc;
    border-top: 1px dashed var(--border);
    margin-top: 10px;
    padding-top: 10px;
}
.loading {
    text-align: center;
    padding: 40px;
    color: var(--secondary);
    font-size: 16px;
}
.loading::after {
    content: '';
    display: inline-block;
    width: 20px;
    height: 20px;
    border: 3px solid rgba(59, 130, 246, 0.3);
    border-radius: 50%;
    border-top-color: var(--primary);
    animation: spin 1s ease-in-out infinite;
    margin-left: 10px;
    vertical-align: middle;
}
@keyframes spin {
    to { transform: rotate(360deg); }
}
.error {
    color: var(--error);
    padding: 20px;
    background: #fee2e2;
    border-radius: var(--radius-sm);
    border-left: 4px solid var(--error);
    font-size: 15px;
}
.copy-btn {
    background: var(--neutral);
    border: 1px solid var(--border);
    padding: 6px 12px;
    border-radius: var(--radius-sm);
    cursor: pointer;
    margin-left: 12px;
    font-size: 14px;
    color: var(--secondary);
    transition: var(--transition);
    display: inline-flex;
    align-items: center;
    gap: 4px;
}
.copy-btn::before {
    content: '📋';
}
.copy-btn:hover {
    background: #e2e8f0;
    color: var(--primary);
    border-color: var(--primary-light);
}
footer {
    text-align: center;
    padding: 25px 0;
    color: var(--secondary);
    font-size: 14px;
    border-top: 1px solid var(--border);
    margin-top: 40px;
}
@media (max-width: 768px) {
    .header h1 {
        font-size: 2rem;
    }
    
    .card {
        padding: 30px 20px;
    }
    
    .query-form {
        flex-direction: column;
        gap: 12px;
    }
    
    #ipInput, #queryBtn {
        width: 100%;
    }
    
    .info-item {
        flex-direction: column;
        align-items: flex-start;
        gap: 4px;
    }
    
    .info-label {
        width: 100%;
        margin-bottom: 4px;
    }
}
</style>
</head>
<body>
<div class="container">
    <div class="header">
        <h1>IP地址查询工具</h1>
        <p>📍 精准查询IPv4/IPv6地址、域名或URL的详细地理位置与运营商信息</p>
    </div>
    <div class="card">
        <div class="query-form">
            <input type="text" id="ipInput" placeholder="输入IPv4/IPv6地址或域名（自动识别）" />
            <button id="queryBtn">立即查询</button>
        </div>
        <div class="quick-actions">
            <button class="quick-btn active" id="localIpv4Btn" onclick="quickQuery('', 'ipv4', event)">本机IPv4</button>
            <button class="quick-btn" onclick="quickQuery('', 'ipv6', event)">本机IPv6</button>
            <button class="quick-btn" onclick="quickQuery('baidu.com', 'ipv4', event)">baidu.com</button>
            <button class="quick-btn" onclick="quickQuery('8.8.8.8', 'ipv4', event)">Google DNS</button>
            <button class="quick-btn" onclick="quickQuery('2409:8a20:2000:5678::1', 'ipv6', event)">IPv6示例</button>
            <button class="quick-btn" onclick="checkProxyAccess(event)">代理检测</button>
        </div>
    </div>
    <div class="result-container">
        <div id="result">
            <div class="loading">正在获取本机IPv4信息...</div>
        </div>
        <div id="loading" class="loading" style="display:none;">正在查询中</div>
    </div>
</div>
<footer>浙ICP备2024134439号</footer>

<script>
let currentQueryType = 'ipv4'; // 默认查询类型：IPv4

// 1. 本机IPv4查询（单独调用ipv4专属API）
async function fetchLocalIPv4() {
    const url = 'https://ip.hxsec.cn/api';
    const res = await fetch(url);
    if (!res.ok) throw new Error(`接口响应失败（状态码：${res.status}）`);
    const data = await res.json();
    if (data.code !== 0) throw new Error(data.message || '本机IPv4查询失败');
    return {
        code: 0,
        data: {
            ip: data.data.ip || '未知',
            country: data.data.country || '未知',
            province: data.data.province || '未知',
            city: data.data.city || '未知',
            // 修复：如果district为空字符串则不显示，不设置默认值
            district: data.data.district,
            as: data.data.as || '未知',
            addr: data.data.addr || '未知',
            latitude: data.data.latitude || '未知',
            longitude: data.data.longitude || '未知'
        }
    };
}

// 2. 本机IPv6查询（调用双栈API）
async function fetchLocalIPv6() {
    const url = 'https://ipapi.hx99.net/ip';
    const res = await fetch(url);
    if (!res.ok) throw new Error(`接口响应失败（状态码：${res.status}）`);
    const data = await res.json();
    if (data.code !== 0) throw new Error(data.message || '本机IPv6查询失败');
    return {
        code: 0,
        data: {
            ip: data.data.ip || '未知',
            country: data.data.country || '未知',
            province: data.data.province || '未知',
            city: data.data.city || '未知',
            district: data.data.district,
            as: data.data.as || '未知',
            addr: data.data.addr || '未知',
            latitude: data.data.latitude || '未知',
            longitude: data.data.longitude || '未知'
        }
    };
}

// 3. 通用IP/域名查询（沿用原有双栈API）
async function fetchIPInfo(ip = '') {
    const url = ip ? `https://ipapi.hx99.net/ip/${encodeURIComponent(ip)}` : 'https://ipapi.hx99.net/ip';
    const res = await fetch(url);
    if (!res.ok) throw new Error(`接口响应失败（状态码：${res.status}）`);
    const data = await res.json();
    if (data.code !== 0) throw new Error(data.message || '查询失败');
    return data;
}

// 4. 从URL提取域名
function extractDomainFromURL(url) {
    try {
        const urlObj = new URL(url);
        return urlObj.hostname;
    } catch (e) {
        const match = url.match(/^https?:\/\/([^\/\?#]+)/i);
        return match ? match[1] : null;
    }
}

// 5. 第三方DNS API解析域名
async function resolveDomainByPublicDNS(domain) {
    const dnsApis = [
        {
            url: `https://dns.alidns.com/resolve?name=${encodeURIComponent(domain)}&type=A&format=json`,
            parse: (data) => data.Answer?.filter(item => item.type === 1).map(item => item.data) || []
        },
        {
            url: `https://119.29.29.29/d?dn=${encodeURIComponent(domain)}&type=A`,
            parse: (data) => data.Answer?.split(';').filter(ip => isValidIP(ip)) || []
        },
        {
            url: `https://114.114.114.114/resolve?name=${encodeURIComponent(domain)}&type=A&format=json`,
            parse: (data) => data.Answer?.filter(item => item.type === 1).map(item => item.data) || []
        }
    ];
    for (const { url, parse } of dnsApis) {
        try {
            const response = await fetch(url, { timeout: 5000 });
            const data = await response.json();
            const ips = parse(data);
            if (ips.length > 0) return ips;
        } catch (e) {
            console.log(`DNS API ${url} 调用失败，尝试下一个`);
            continue;
        }
    }
    throw new Error('所有公共DNS服务均无法解析该域名');
}

// 6. IP有效性验证
function isValidIP(ip) {
    const ipv4Regex = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
    const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$|^([0-9a-fA-F]{1,4}:){1,7}:$|^([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}$/;
    return ipv4Regex.test(ip) || ipv6Regex.test(ip);
}

// 7. 域名有效性验证
function isValidDomain(domain) {
    const domainRegex = /^((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.)+[a-zA-Z]{2,6}$/;
    return domainRegex.test(domain);
}

// 8. 快捷查询功能
function quickQuery(ip, type, e, isInitialLoad = false) {
    document.getElementById('ipInput').value = ip;
    currentQueryType = type;
    
    if (!isInitialLoad) {
        document.querySelectorAll('.quick-btn').forEach(b => b.classList.remove('active'));
        if (e && e.target) e.target.classList.add('active');
    }
    
    queryIP();
}

// 9. 结果格式化（修复地理位置显示）
function formatResult(data, isLocalIPv6Query = false) {
    const info = data.data;
    const resolved = data.resolved || {};
    
    // 修复：过滤空字符串和null的地理位置信息
    const locationParts = [
        info.country, 
        info.province, 
        info.city, 
        info.district
    ].filter(part => part && part.trim() !== '' && part !== '未知');
    
    const location = locationParts.join(" ") || "未知";
    const isIPv6 = info.ip.includes(":");
    let html = "";

    // 域名解析结果（如有）
    if (resolved.original) {
        html += `
        <div class="result-card">
            <div class="info-item">
                <div class="info-label">域名解析</div>
                <div class="info-value">${resolved.original} → <strong>${resolved.ip}</strong>
                    <span class="ip-type-badge ipv4-badge">IPv4</span>
                </div>
            </div>
        </div>`;
    }

    // 核心IP信息
    html += `
    <div class="result-card">
        <div class="info-item">
            <div class="info-label">IP地址</div>
            <div class="info-value">
                <strong>${info.ip}</strong>
                <span class="ip-type-badge ${isIPv6 ? "ipv6-badge" : "ipv4-badge"}">${isIPv6 ? "IPv6" : "IPv4"}</span>
                <button class="copy-btn" onclick="copyText('${info.ip}')">复制</button>
            </div>
        </div>
        <div class="info-item"><div class="info-label">地理位置</div><div class="info-value">${location}</div></div>
        <div class="info-item"><div class="info-label">运营商</div><div class="info-value">${info.as || "未知"}</div></div>
        <div class="info-item"><div class="info-label">IP段</div><div class="info-value">${info.addr || "未知"}</div></div>
        <div class="info-item"><div class="info-label">经纬度</div><div class="info-value">${info.latitude || "未知"}, ${info.longitude || "未知"}</div></div>`;

    // 无IPv6网络提示
    if (isLocalIPv6Query && !isIPv6) {
        html += `
        <div class="info-item ipv6-tip">
            <div class="info-label">提示</div>
            <div class="info-value">当前设备无IPv6网络，已自动返回IPv4信息</div>
        </div>`;
    }

    html += `</div>`;
    return html;
}

// 10. 复制功能
function copyText(text) {
    if (navigator.clipboard && window.isSecureContext) {
        navigator.clipboard.writeText(text).then(() => {
            alert("IP地址已成功复制！");
        }).catch(() => fallbackCopyText(text));
    } else {
        fallbackCopyText(text);
    }
}

// 11. 传统复制方法
function fallbackCopyText(text) {
    const textArea = document.createElement("textarea");
    textArea.value = text;
    textArea.style.position = "fixed";
    textArea.style.opacity = "0";
    document.body.appendChild(textArea);
    textArea.select();
    try {
        const successful = document.execCommand('copy');
        alert(successful ? "IP地址已成功复制！" : "复制失败，请手动复制");
    } catch (err) {
        alert("复制失败，请手动复制");
    }
    document.body.removeChild(textArea);
}

// 12. 代理检测功能
async function checkProxyAccess(e) {
    document.querySelectorAll('.quick-btn').forEach(b => b.classList.remove('active'));
    if (e && e.target) e.target.classList.add('active');
    
    const resultDiv = document.getElementById("result");
    const loadingDiv = document.getElementById("loading");
    
    loadingDiv.style.display = "block";
    resultDiv.innerHTML = "";
    
    try {
        const response = await fetch('https://api.vore.top/api/IPdata?ip=');
        if (!response.ok) throw new Error('网络请求失败');
        const data = await response.json();
        
        const isCnIp = data.ipinfo?.cnip;
        const region = data.ipdata?.info1 || '';
        const city = data.ipdata?.info2 || '未知';
        const isp = data.ipdata?.isp || '未知';
        const ip = data.ipinfo?.text || '未知';
        
        const infoList = [
            { label: '当前IP', value: ip },
            { label: '国家/地区', value: region },
            { label: '城市', value: city },
            { label: '运营商', value: isp }
        ];
        
        let accessStatus = '';
        if (isCnIp && !['香港', '澳门', '台湾'].includes(region)) {
            accessStatus = `
                <div class="info-item">
                    <div class="info-label">访问权限</div>
                    <div class="info-value" style="color: var(--error); font-weight: 600;">
                        中国大陆IP，可能无法访问Google/youtube 等部分国际服务！
                    </div>
                </div>
            `;
        } else {
            accessStatus = `
                <div class="info-item">
                    <div class="info-label">访问权限</div>
                    <div class="info-value" style="color: var(--success); font-weight: 600;">
                        非中国大陆IP，可以正常访问Google/youtube 等国际服务！
                    </div>
                </div>
            `;
        }
        
        resultDiv.innerHTML = `
            <div class="result-card">
                <div class="info-item">
                    <div class="info-label">检测类型</div>
                    <div class="info-value"><strong>代理与访问权限检测</strong></div>
                </div>
                ${infoList.map(item => `
                    <div class="info-item">
                        <div class="info-label">${item.label}</div>
                        <div class="info-value">${item.value}</div>
                    </div>
                `).join('')}
                ${accessStatus}
            </div>
        `;
    } catch (error) {
        resultDiv.innerHTML = `<div class="error">检测失败：${error.message}</div>`;
    } finally {
        loadingDiv.style.display = "none";
    }
}

// 13. 核心查询逻辑
async function queryIP() {
    const resultDiv = document.getElementById("result");
    const loadingDiv = document.getElementById("loading");
    const input = document.getElementById("ipInput").value.trim();
    
    loadingDiv.style.display = "block";
    resultDiv.innerHTML = "";
    
    try {
        // 本机IPv4查询
        if (input === "" && currentQueryType === 'ipv4') {
            const data = await fetchLocalIPv4();
            resultDiv.innerHTML = formatResult(data);
            return;
        }
        
        // 本机IPv6查询
        if (input === "" && currentQueryType === 'ipv6') {
            const data = await fetchLocalIPv6();
            resultDiv.innerHTML = formatResult(data, true);
            return;
        }
        
        // 处理URL/域名解析
        if (input.match(/^https?:\/\//)) {
            const domain = extractDomainFromURL(input);
            if (!domain) throw new Error('无法从URL中提取有效域名');
            const ips = await resolveDomainByPublicDNS(domain);
            const targetIP = ips[0];
            const apiData = await fetchIPInfo(targetIP);
            apiData.resolved = { original: input, ip: targetIP };
            resultDiv.innerHTML = formatResult(apiData);
            return;
        }
        
        // 处理域名查询
        if (!isValidIP(input) && isValidDomain(input)) {
            const ips = await resolveDomainByPublicDNS(input);
            const targetIP = ips[0];
            const apiData = await fetchIPInfo(targetIP);
            apiData.resolved = { original: input, ip: targetIP };
            resultDiv.innerHTML = formatResult(apiData);
            return;
        }
        
        // 处理指定IP查询
        const apiData = await fetchIPInfo(input);
        resultDiv.innerHTML = formatResult(apiData);
        
    } catch (error) {
        resultDiv.innerHTML = `<div class="error">查询失败：${error.message}</div>`;
    } finally {
        loadingDiv.style.display = "none";
    }
}

// 14. 事件绑定
document.getElementById('ipInput').addEventListener('keypress', e => {
    if (e.key === 'Enter') queryIP();
});
document.getElementById('queryBtn').addEventListener('click', queryIP);
window.addEventListener("load", () => {
    quickQuery('', 'ipv4', null, true);
});
</script>
</body>
</html>
